#include <cassert>
#include <climits>
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <limits.h>
#include <list>
#include <log4cplus/logger.h>
#include <log4cplus/consoleappender.h>
#include <log4cplus/fileappender.h>
#include <map>
#include <stdlib.h>
#include <sys/stat.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}

// #include "rtp_writer.hpp"
#include "rtp_analyzer.hpp"

static const char* shortOptions = "hgRCSE:AaVi:r:";

enum Options {
    OptionHelp          = 'h',
    OptionLogFile       = 'g',
    OptionNoAudio       = 'A',
    OptionNoVideo       = 'V',
    OptionNoRTP         = 'R',
    OptionNoRTCP        = 'C',
    OptionNoSDP         = 'S',
    OptionNoCODEC       = 'E',
    OptionNoFrame       = 'a',
    OptionROLL          = 'r',
    OptionInput         = 'i'
};

static const struct option longOptions[] = {
        {   "help",         no_argument,        nullptr,    OptionHelp      },
        {   "log",          required_argument,  nullptr,    OptionLogFile   },
        {   "no-audio",     no_argument,        nullptr,    OptionNoAudio   },
        {   "no-video",     no_argument,        nullptr,    OptionNoVideo   },
        {   "no-rtp",       no_argument,        nullptr,    OptionNoRTP     },
        {   "no-rtcp",      no_argument,        nullptr,    OptionNoRTCP    },
        {   "no-sdp",       no_argument,        nullptr,    OptionNoSDP     },
        {   "no-codec",     no_argument,        nullptr,    OptionNoCODEC   },
        {   "no-frame",     no_argument,        nullptr,    OptionNoFrame   },
        {   "roll",         required_argument,  nullptr,    OptionROLL      },
        {   "input",        required_argument,  nullptr,    OptionInput     },
        {   0,              0,                  0,          0               },
};

void printUsage(const char* progName) {
    fprintf(stderr, "Usage: %s [options]\n", progName);
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -h,--help                 print this                 \n");
    fprintf(stderr, "  -g,--log <log-file>       write log to this file     \n");
    fprintf(stderr, "  -A,--no-audio             ignore audio               \n");
    fprintf(stderr, "  -V,--no-video             ignore video               \n");
    fprintf(stderr, "  -R,--no-rtp               ignore rtp               \n");
    fprintf(stderr, "  -C,--no-rtcp              ignore rtcp               \n");
    fprintf(stderr, "  -S,--no-sdp               ignore sdp               \n");
    fprintf(stderr, "  -E,--no-codec             ignore codec               \n");
    fprintf(stderr, "  -a,--no-frame             ignore frame               \n");
    fprintf(stderr, "  -r,--roll <number>        log roll number            \n");
    fprintf(stderr, "  -i,--input <tlv>          input tlv file             \n");
}

int main(int argc, char *argv[]) {
    // medooze logging
    Logger::EnableDebug(false);
    Logger::EnableUltraDebug(false);

    std::string logFile;
    bool log_audio = true;
    bool log_video = true;
    bool log_rtp = true;
    bool log_rtcp = true;
    bool log_sdp = true;
    bool log_codec = true;
    bool log_frame = true;
    int  roll_number = INT32_MAX;
    bool debug = false;
    std::string input_file;
    std::string output_file;

    int optionIndex = 0;
    int c;
    while((c = getopt_long(argc, argv, shortOptions, longOptions, &optionIndex)) != -1) {
        switch(c) {
            case OptionHelp:
                printUsage(argv[0]);
                return 0;
            case OptionLogFile:
                logFile = std::string(optarg);
                break;
            case OptionNoAudio: {
                log_audio = false;
            } break;
            case OptionNoVideo: {
                log_video = false;
            } break;
            case OptionNoRTP: {
                log_rtp = false;
            } break;
            case OptionNoRTCP: {
                log_rtcp = false;
            } break;
            case OptionNoSDP: {
                log_sdp= false;
            } break;
            case OptionNoCODEC: {
                log_codec = false;
            } break;
            case OptionNoFrame: {
                log_frame = false;
            } break;
            case OptionROLL: {
                roll_number = atoi(optarg);
            } break;
            case OptionInput: {
                size_t l = strlen(optarg);
                if (l < 5 || strcasecmp(optarg + l - 3, "tlv") != 0) {
                    std::cerr << "input must be tlv" << std::endl;
                    exit(-1);
                }
                char fn_in[PATH_MAX] = {0,};
                realpath(optarg, fn_in);
                struct stat buf;
                int err = stat(fn_in, &buf);
                if (err != 0) {
                    std::cerr << "cannot stat " << std::string(fn_in) << std::endl;
                    exit(-1);
                }

                input_file = std::string(fn_in);
            } break;
            case '?':
                if (optopt == 'g' || optopt == 'f' || optopt == 't' || optopt == 'i' || optopt == 'o' ){
                    fprintf (stderr, "Option -%c requires an argument.\n", optopt);
                } else if (isprint (optopt)){
                    fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                } else{
                    fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
                }
                printUsage(argv[0]);
                return 0;
            default:
                fprintf (stderr, "Unknown option `-%c'.\n", optopt);
                printUsage(argv[0]);
                return 0;
        }
    }

    log4cplus::initialize();
    auto rootLogger = log4cplus::Logger::getRoot();
    rootLogger.setLogLevel(log4cplus::ALL_LOG_LEVEL);

    auto consoleAppender = log4cplus::SharedAppenderPtr(new log4cplus::ConsoleAppender());
    consoleAppender->setLayout(std::auto_ptr<log4cplus::Layout>(new log4cplus::PatternLayout("%m %n")));
    consoleAppender->setThreshold(log4cplus::DEBUG_LOG_LEVEL);

    rootLogger.addAppender(consoleAppender);

    av_register_all();
    avcodec_register_all();

    if (!logFile.empty()) {
        auto debugAppender = log4cplus::SharedAppenderPtr(new log4cplus::RollingFileAppender(logFile, 100*1024*1024, 10, false));
        debugAppender->setLayout(std::auto_ptr<log4cplus::Layout>(new log4cplus::PatternLayout("%-5p %m %n")));
        debugAppender->setThreshold(log4cplus::ALL_LOG_LEVEL);
        rootLogger.addAppender(debugAppender);
    }

    // FMT_INFO("tlv-analyzer begin", FMT());
    Filters::shared filter = std::make_shared<Filters>();
    filter->setMask(
                    (log_audio ? Filters::Mask::AUDIO : Filters::Mask::NONE)
                    | (log_video ? Filters::Mask::VIDEO : Filters::Mask::NONE)
                    | (log_codec ? Filters::Mask::CODEC : Filters::Mask::NONE)
                    );
    
    filter->setLogLevel(0
                    | (log_frame ? Filters::LogLv::FRAME : 0)
                    | (log_rtp ? Filters::LogLv::RTP : 0)
                    | (log_rtcp ? Filters::LogLv::RTCP : 0)
                    | (log_sdp ? Filters::LogLv::SDP : 0)
                    );
    
    filter->setRollNumber(roll_number);

    RTPAnalyzer::Create()->analyzer(input_file, filter);

    return 0;
}
